package com.codelee.soybean;

import com.codelee.soybean.annotation.Column;
import com.codelee.soybean.annotation.Exclude;
import com.codelee.soybean.annotation.Id;
import com.codelee.soybean.annotation.Table;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * Object Oriented queryengine
 * Created by lic on 15/6/6.
 */
public class OOQueryEngine extends AbstractEngine{

    private final static QueryEngine engine = new QueryEngine();

    /**
     * save bean
     * @param conn
     * @param obj
     * @throws Throwable
     */
    public void save(Connection conn, Object obj) throws Throwable{

        PreparedStatement ps = null;
        try {

            ps = createInsertPs(conn,obj);
            ps.executeUpdate();

        }catch (Throwable e){

            e.printStackTrace();
            throw e;

        }finally {
            try {

                SoybeanUtils.close(ps);
                if(this.autoCommit){
                    SoybeanUtils.close(conn);

                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * update bean
     * @param conn
     * @param obj
     * @throws Throwable
     */
    public void update(Connection conn, Object obj) throws Throwable{
        PreparedStatement ps = null;
        try {

            ps = createUpdatePs(conn, obj);
            ps.executeUpdate();

        }catch (Throwable e){

            e.printStackTrace();
            throw e;

        }finally {
            try {

                SoybeanUtils.close(ps);
                if(this.autoCommit){
                    SoybeanUtils.close(conn);

                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * save or update bean
     * @param conn
     * @param obj
     * @throws Throwable
     */
    public void saveOrUpdate(Connection conn, Object obj) throws Throwable{
        Class c = obj.getClass();
        Field primaryField = getPrimaryField(c);
        Object value = primaryField.get(obj);
        if(value == null || value.equals(new Integer(0)) || value.equals(new Long(0))){
            save(conn,obj);
        }else{
            update(conn,obj);
        }
    }

    /**
     * select all
     * @param conn
     * @param clazz
     * @param <T>
     * @return
     * @throws Throwable
     */
    public <T>List<T> listAll(Connection conn, Class<? extends T> clazz) throws Throwable{
        String tableName = getTableName(clazz);
        String sql = "select * from " + tableName;
        return engine.toBeanList(conn, sql , clazz);
    }

    /**
     * select by id
     * @param conn
     * @param id
     * @param clazz
     * @param <T>
     * @return
     * @throws Throwable
     */
    public <T>T get(Connection conn, Class<? extends T> clazz , Object id) throws Throwable{
        String tableName = getTableName(clazz);
        Field primaryField = getPrimaryField(clazz);
        String columnName = getColumnName(primaryField);
        String sql = "select * from " + tableName + " where " + columnName + " = ?";
        return engine.toBean(conn, sql, clazz, id);
    }

    /**
     * delete by id
     * @param conn
     * @param clazz
     * @param id
     * @throws Throwable
     */
    public void remove(Connection conn , Object obj) throws Throwable{
        String tableName = getTableName(obj.getClass());
        Field primaryField = getPrimaryField(obj.getClass());
        String columnName = getColumnName(primaryField);
        String sql = "delete  from " + tableName + " where " + columnName + " = ?";

        engine.update(conn, sql, primaryField.get(obj));
    }

    private PreparedStatement createUpdatePs(Connection conn, Object obj) throws Exception{
        Class c = obj.getClass();
        String tableName = getTableName(c);
        StringBuffer sqlBuffer = new StringBuffer();
        sqlBuffer.append("update " + tableName + " set ");
        Field[] fields = c.getDeclaredFields();
        List values = new ArrayList();
        Object primaryValue = null;
        String primaryName = null;
        for(Field field : fields){
            field.setAccessible(true);
            if(Modifier.isStatic(field.getModifiers())
                    || Modifier.isFinal(field.getModifiers())){
                continue;
            }
            if(field.getAnnotation(Exclude.class) != null){
                continue;
            }
            Id primary = field.getAnnotation(Id.class);//主键
            Object value = field.get(obj);
            String colName = getColumnName(field);


            if(primary != null){
                if(value == null || value.equals(new Integer(0)) || value.equals(new Long(0))){
                    throw new SQLException("there is no primary key value");

                }else{
                    primaryName = colName;
                    primaryValue = value;
                    continue;
                }

            }

            if(value == null || value.equals(new Integer(0))){
                continue;
            }
            sqlBuffer.append(colName + " = ?, ");
            values.add(value);


        }
        sqlBuffer.deleteCharAt(sqlBuffer.lastIndexOf(",")).append("where " + primaryName + " = ?");

        values.add(primaryValue);
        System.out.println(sqlBuffer);
        return super.preparedStatement(conn, sqlBuffer.toString(), values.toArray());

    }

    private PreparedStatement createInsertPs(Connection conn,Object obj) throws Exception{
        Class c = obj.getClass();
        String tableName = getTableName(c);
        StringBuffer sqlBuffer = new StringBuffer();
        sqlBuffer.append("insert into " + tableName + "(");
        Field[] fields = c.getDeclaredFields();
        List values = new ArrayList();
        for(Field field : fields){
            field.setAccessible(true);
            if(Modifier.isStatic(field.getModifiers())
                    || Modifier.isFinal(field.getModifiers())){
                continue;
            }
            if(field.getAnnotation(Exclude.class) != null){
                continue;
            }

            Id primary = field.getAnnotation(Id.class);//主键
            String colName = getColumnName(field);

            Object value = field.get(obj);
            if(primary != null){
                if(value == null || value.equals(new Integer(0)) || value.equals(new Long(0))){
                    continue;
                }

            }

            sqlBuffer.append(colName + ",");

            values.add(value);

        }
        sqlBuffer.deleteCharAt(sqlBuffer.length() - 1).append(") values(");
        for(int i=0;i<values.size();i++){
            sqlBuffer.append("?,");
        }

        sqlBuffer.deleteCharAt(sqlBuffer.length()-1).append(")");
        return super.preparedStatement(conn,sqlBuffer.toString(),values.toArray());

    }


    private String getTableName(Class c){
        String tableName = c.getName().split("\\.")[c.getName().split("\\.").length-1].toLowerCase();
        Table table = (Table)c.getAnnotation(Table.class);
        if(table != null){
            tableName = table.value();
        }
        return tableName;
    }

    private Field getPrimaryField(Class c) throws Exception{
        Field[] fields = c.getDeclaredFields();
        Field primaryField = null;
        for(Field field : fields){
            if(field.getAnnotation(Id.class) != null){
                primaryField = field;
            }
        }
        if(primaryField == null){
            throw new SQLException("please put the @Id onto the primary field");
        }
        primaryField.setAccessible(true);

        return primaryField;
    }

    private String getColumnName(Field f){
        String colName = f.getName();
        Column column = f.getAnnotation(Column.class);
        return column == null ? colName : column.value();
    }

    @Override
    public void setAutoCommit(boolean autoCommit) {
        super.setAutoCommit(autoCommit);
        engine.setAutoCommit(autoCommit);
    }
}
